home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Pascal / Snippets / Floating / Floating.p
Encoding:
Text File  |  1994-04-16  |  15.6 KB  |  338 lines  |  [TEXT/R*ch]

  1. unit Floating;
  2.  
  3. {A set of routines to handle floating windows. For use with THINK Pascal 4.0}
  4. {Written by F. Pottier, june 1993. E-mail : pottier@clipper.ens.fr}
  5.  
  6. {Originally based on a set of C routines written by Patrick Doane (America OnLine : Patrick5) }
  7. {Thanks go to him, and also to Troy Gaul for placing his Infinity Windoid in the public domain}
  8.  
  9. {This code is placed in the public domain. It may be used by anybody, for any purposes. It would be kind to give credits to me}
  10. {as well as to the aforementioned people, though.}
  11. {If you experience any problems, or have ideas for enhancements, tell me about it. I can't guarantee I'll spend much}
  12. {time on it, since I'm not a professional programmer, but I'll take a look at it.}
  13.  
  14. { --- Some general comments ------------------------------------------------------------------------------------------ }
  15.  
  16. {The Window Manager doesn't know about floating windows. To obtain floating windows in a program,}
  17. {one has to handle them 'by hand', which means calling low-level Window Manager routines to place the windows}
  18. {in their correct positions after each event.}
  19. {The frequently used, and now to be avoided, Window Manager routines are :}
  20. {        SelectWindow        }
  21. {        CloseWindow            }
  22. {        HideWindow            }
  23. {        ShowWindow            }
  24. {        DisposeWindow        }
  25. {        DragWindow            }
  26. {Those routines don't know about floating windows, so calling them would put the floating windows behind normal windows.}
  27. {You also have to be careful with GetNewWindow. You can't call GetNewWindow(ID, wStorage, Pointer(-1)) because that}
  28. {would put the new window in the front. Rather, we call GetNewWindow(ID, wStorage, nil) which places the window in the}
  29. {back, and then call our home-made routine, SelectTheWindow.}
  30.  
  31. {At every point in the program, we maintain three global variables : topFloat, bottomFloat and topWindow.}
  32. {topFloat holds the top floating window, nil if there is none. Same thing for bottomFloat with the bottom floating window,}
  33. {and for topWindow with the top regular window. These three global variables, along with the Window Manager's window}
  34. {list, are enough to handle the behavior of our windows.}
  35. {Hidden windows are placed behind all others, so they don't count when determining those variables' values.}
  36.  
  37. {In order to distinguish between floating windows and regular ones, we choose a different windowKind for the former.}
  38.  
  39. {To make hiliting and unhiliting floating windows simpler, I chose to use a WDEF that always draws windoids in a hilited}
  40. {state (e.g. Infinity Windoid with the Always Hilite option). This way, I don't have to worry at all about it.}
  41.  
  42. {A final remark about modeless dialogs : modeless dialogs and floating windows don't go well together. The Dialog Manager}
  43. {is confused by floating windows. IsDialogEvent and DialogSelect don't work because they use FrontWindow to find the dialog.}
  44. {It is easy to rewrite a modified version of isDialogEvent. It is already trickier for DialogSelect. Maybe it would be simpler}
  45. {to use windows with controls instead of modeless dialogs. Or maybe it's possible to have DialogSelect and isDialogEvent}
  46. {work by patching FrontWindow ? }
  47. {If somebody comes up with a good idea on this topic, please tell me about it.}
  48.  
  49. { --- How to use the unit, in short -------------------------------------------------------------------------------------- }
  50.  
  51. {- Call InitFloats once before opening any windows.}
  52. {- To create a new window, call GetNewWindow(id, storage, NIL). Then call SelectTheWindow if it's a regular window,}
  53. {   otherwise call MakeFloat.}
  54. {- To determine whether a given window is a floating one, call isFloating}
  55. {- Instead of SelectWindow[and ShowWindow], HideWindow, DisposeWindow, DragWindow, use SelectTheWindow, HideTheWindow,}
  56. {   DIsposeTheWindow and DragTheWindow.}
  57. {- Upon a receiving a suspend event, you may call HideFloats. Use ShowFloats when receiving a resume event.}
  58. {- Instead of calling FrontWindow, you can read the values of the variables topFloat and topWindow. They should be up-to-date}
  59. {   at any point in the program.}
  60.  
  61. { --- Unit interface --------------------------------------------------------------------------------------------------- }
  62.  
  63. interface
  64.  
  65.     procedure InitFloats;                                                                            {initializes the global variables}
  66.  
  67.     function IsFloating (whichWindow: WindowPtr): Boolean;                        {tells whether a window is floating}
  68.     procedure MakeFloat (theWindow: WindowPtr);                                    {turns a newly created window into a floating window}
  69.  
  70.     procedure HideTheWindow (whichWindow: WindowPtr);                        {equivalents for the old Toolbox calls}
  71.     procedure DisposeTheWindow (whichWindow: WindowPtr);
  72.     procedure SelectTheWindow (whichWindow: WindowPtr);
  73.     procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord);
  74.     procedure DisposeTheDialog (whichDialog: DialogPtr);
  75.  
  76.     procedure HideFloats;                                                                            {hides all floating windows}
  77.     procedure ShowFloats;                                                                        {shows all floating windows}
  78.  
  79.     var
  80.         bottomFloat, topFloat, topWindow: WindowPtr;
  81.         hasColorQD: Boolean;                                                                        {initialized by InitFloats}
  82.  
  83. { -------------------------------------------------------------------------------------------------------------------- }
  84.  
  85. implementation
  86.  
  87.     const
  88.         kFloatingKind = 317;                                                                            {arbitrary constant for floating windows' windowKind}
  89.  
  90. {ActivateWindow hilites/unhilites the window and then generates an activate/deactivate event for it.}
  91. {In order to generate activate events, it mucks with low-mem globals. It would be cleaner to directly call the activate}
  92. {routine for the window. The advantage of this method is that it also works with modeless dialogs : the Dialog Manager}
  93. {doesn't know that a dialog is active unless it actually receives an activate event.}
  94.  
  95.     procedure ActivateWindow (window: WindowPtr; on: Boolean);
  96.         const
  97.             CurActivate = $0A64;                                                                    {writing a WindowPtr in those globals generates}
  98.             CurDeactive = $0A68;                                                                    {an activate or deactivate event}
  99.         var
  100.             p: ^WindowPtr;
  101.     begin
  102.         if window <> nil then begin
  103.             HiliteWindow(window, on);                                                            {hilite the window}
  104.             if on then
  105.                 p := Pointer(CurActivate)                                                            {generate the appropriate event}
  106.             else
  107.                 p := Pointer(CurDeactive);
  108.             p^ := window;
  109.         end;
  110.     end;
  111.  
  112. {InitFloats is to be called at the beginning of the program, before creating any windows. It sets our global variables to nil.}
  113.  
  114.     procedure InitFloats;
  115.         var
  116.             e: OSErr;
  117.             response: longint;
  118.     begin
  119.         topFloat := nil;
  120.         bottomFloat := nil;
  121.         topWindow := nil;
  122.  
  123.         e := Gestalt(gestaltQuickdrawVersion, response);
  124.         hasColorQD := (e = noErr) and (response >= gestalt8bitQD);
  125.     end;
  126.  
  127. {IsFloating and isVisible are little, foolprof routines that tell whether a window is floating or visible.}
  128. {Using them avoids errors such as examining WindowPeek(whichWindow)^.visible when whichWindow = nil}
  129.  
  130.     function IsFloating (whichWindow: WindowPtr): Boolean;
  131.     begin
  132.         if whichWindow = nil then
  133.             IsFloating := false
  134.         else
  135.             IsFloating := WindowPeek(whichWindow)^.windowKind = kFloatingKind;
  136.     end;
  137.  
  138.     function isVisible (whichWindow: WindowPtr): Boolean;
  139.     begin
  140.         if whichWindow = nil then
  141.             isVisible := false
  142.         else
  143.             isVisible := WindowPeek(whichWindow)^.visible;
  144.     end;
  145.  
  146. {UpdateFloats updates the topFloat and bottomFloat variables by walking down the window list. It then determines}
  147. {topWindow by taking the first visible window after bottomFloat.}
  148.  
  149.     procedure UpdateVars;
  150.         var
  151.             theWindow: WindowPtr;
  152.     begin
  153.         theWindow := FrontWindow;                                                            {start from the frontmost window}
  154.         if IsFloating(theWindow) then begin                                            {if it's floating, then it's topFloat}
  155.             topFloat := theWindow;
  156.             while (theWindow <> nil) and isFloating(theWindow) do begin    {walk down the list until we find a regular window}
  157.                 bottomFloat := theWindow;                                                    {or the end of the list}
  158.                 theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow);
  159.             end;
  160.  
  161.             while (theWindow <> nil) and (not isVisible(theWindow)) do    {find the next visible window after bottomFloat}
  162.                 theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow);
  163.             topWindow := theWindow;
  164.         end
  165.         else begin                                                                                    {if the frontmost window is a regular one, then there}
  166.             topFloat := nil;                                                                            {are no floating windows and topWindow = FrontWindow}
  167.             bottomFloat := nil;
  168.             topWindow := FrontWindow;
  169.         end;
  170.     end;
  171.  
  172. {MakeFloat turns a newly created window into a floating window and brings it to the front}
  173. {The window must have been created via a call to [Get]NewWindow(id, wStorage, nil).}
  174.  
  175.     procedure MakeFloat (theWindow: WindowPtr);
  176.     begin
  177.         BringToFront(theWindow);                                                                {Bring the window to the front without unhiliting other}
  178.         ShowHide(theWindow, true);                                                            {windows. Make it visible in case it isn't}
  179.         if topFloat = nil then
  180.             bottomFloat := theWindow;                                                        {update bottomFloat et topFloat}
  181.         topFloat := theWindow;
  182.         WindowPeek(theWindow)^.windowKind := kFloatingKind;                    {remember that this window should float}
  183.     end;
  184.  
  185. {ReactToRemoval handles a few necessary steps after killing whichWindow. underWindow is the window that was}
  186. {under whichWindow (i.e. next to it in the window list).}
  187.  
  188.     procedure ReactToRemoval (underWindow: WindowPeek);
  189.     begin
  190.         while (underWindow <> nil) and (not isVisible(WindowPtr(underWindow))) do
  191.             underWindow := underWindow^.nextWindow;                                {activate the next visible window under whichWindow}
  192.         ActivateWindow(WindowPtr(underWindow), true);
  193.         UpdateVars;                                                                                    {update the variables}
  194.     end;
  195.  
  196. {There was a bug in HideTheWindow, reported and fixed by Ralph ? (I stupidly lost his name and address). The problem was that}
  197. {HideWindow(whichWindow) moves the next visible window to the top of the list, so ReactToRemoval will end up activating the}
  198. {second visible window, and we will have two active windows. The fix is to call SendBehind to send whichWindow behind all}
  199. {other windows prior to hiding it. This way HideWindow has no side effects.}
  200.  
  201.     procedure HideTheWindow (whichWindow: WindowPtr);
  202.         var
  203.             underWindow: WindowPeek;                                                        {we must determine which window is under whichWindow}
  204.     begin                                                                                                {before hiding it, because HideWindow sends it to the back}
  205.         underWindow := WindowPeek(whichWindow)^.nextWindow;                {thus changing the value of nextWindow}
  206.         SendBehind(whichWindow, nil);
  207.         HideWindow(whichWindow);
  208.         ReactToRemoval(underWindow);
  209.     end;
  210.  
  211.     procedure DisposeTheWindow (whichWindow: WindowPtr);
  212.         var
  213.             underWindow: WindowPeek;
  214.     begin
  215.         underWindow := WindowPeek(whichWindow)^.nextWindow;                {same thing as HideTheWindow, with DisposeWindow instead}
  216.         DisposeWindow(whichWindow);
  217.         ReactToRemoval(underWindow);
  218.     end;
  219.  
  220.     procedure DisposeTheDialog (whichDialog: DialogPtr);
  221.         var
  222.             underWindow: WindowPeek;
  223.     begin
  224.         underWindow := WindowPeek(whichDialog)^.nextWindow;                {same thing as HideTheWindow, with DisposeDialog instead}
  225.         DisposeDialog(whichDialog);
  226.         ReactToRemoval(underWindow);
  227.     end;
  228.  
  229. {SelectTheWindow is the most important routine. Here are the steps to be taken :}
  230. {If whichWindow is not visible, show it.}
  231. {- If whichWindow is a floating window, just bring it to the front.}
  232. {- If it is a regular window and there are no floating windows, bring it to the front, activate it, deactivate the former front window}
  233. {- If it is a regular window and there are floating windows, send it behind bottomFloat, activate it, deactivate the old topWindow}
  234. {Finally, update the global variables.}
  235.  
  236.     procedure SelectTheWindow (whichWindow: WindowPtr);
  237.         var
  238.             wPeek: WindowPeek;
  239.     begin
  240.         wPeek := WindowPeek(whichWindow);
  241.  
  242.         if not wPeek^.visible then                                                            {make the window visible if necessary}
  243.             ShowHide(whichWindow, true);
  244.  
  245.         if IsFloating(whichWindow) then                                                     {a floating window just needs to be brought to the front}
  246.             BringToFront(whichWindow)
  247.         else if whichWindow <> topWindow then begin                            {regular window. If it isn't topWindow we have to react}
  248.             if bottomFloat <> nil then begin                                                {if there are floating windows, then send whichWindow}
  249.                 SendBehind(whichWindow, bottomFloat);                                {behind bottomFloat}
  250.                 PaintOne(wPeek, wPeek^.strucRgn);                                        {These two low-level calls are necessary to generate}
  251.                 CalcVis(wPeek);                                                                    {update events and maintain the windows' visRgns}
  252.             end
  253.             else
  254.                 BringToFront(whichWindow);                                                    {no floating windows : just call BringToFront}
  255.  
  256.             ActivateWindow(whichWindow, true);                                        {activate the new front window}
  257.             ActivateWindow(topWindow, false);                                            {and deactivate the old one}
  258.         end;
  259.  
  260.         UpdateVars;                                                                                    {finally update our vars}
  261.     end;
  262.  
  263. {DragTheWindow is to be called instead of DragWindow, because DragWindow brings the window to the front unless the command}
  264. {key is down. We use DragGrayRgn to drag a gray outline of the window. We clip the dragging to a region consisting of the whole}
  265. {desktop, minus the windows that are in front of the window we're dragging.}
  266. {To emulate DragWindow's behavior, we send the window to the front, unless the command key is down.}
  267.  
  268.     procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord);
  269.         const
  270.             cancelDrag = $80008000;                                                            {returned by DragGrayRgn if the user aborts the drag}
  271.         var
  272.             savePort: GrafPtr;
  273.             wPort: GrafPort;
  274.             thePoint: point;
  275.             newLoc: longint;
  276.             theWindow: WindowPeek;
  277.             dragRect: rect;
  278.             dragRgn: RgnHandle;
  279.     begin
  280.         if BitAnd(event.modifiers, cmdKey) = 0 then                                {unless the command key is depressed, select the window}
  281.             SelectTheWindow(whichWindow);                                                {before doing the dragging}
  282.  
  283.         GetPort(savePort);                                                                        {save the grafport}
  284.         SetPort(whichWindow);
  285.         thePoint := whichWindow^.portRect.topLeft;
  286.         LocalToGlobal(thePoint);                                                                {remember the window's original position}
  287.  
  288.         dragRgn := NewRgn;                                                                        {put the window's strucRgn in a new region}
  289.         CopyRgn(WindowPeek(whichWindow)^.strucRgn, dragRgn);                {we can't pass the strucRgn directly to DragGrayRgn}
  290.                                                                                                             {because DragGrayRgn modifies the region}
  291.         OpenPort(@wPort);                                                                        {open a port to draw the gray outline}
  292.         CopyRgn(GetGrayRgn, wPort.visRgn);                                            {set its visRgn to the whole screen, minus the strucRgns}
  293.         theWindow := WindowPeek(FrontWindow);                                        {of all the windows in front of whichWindow}
  294.         while theWindow <> WindowPeek(whichWindow) do begin
  295.             DiffRgn(wPort.visRgn, theWindow^.strucRgn, wPort.visRgn);
  296.             theWindow := theWindow^.nextWindow;
  297.         end;
  298.  
  299.         dragRect := GetGrayRgn^^.rgnBBox;
  300.         InsetRect(dragRect, 4, 4);
  301.  
  302.         newLoc := DragGrayRgn(dragRgn, event.where, dragRect, dragRect, noConstraint, nil);
  303.         if (newLoc <> cancelDrag) and (newLoc <> 0) then
  304.             MoveWindow(whichWindow, thePoint.h + LoWord(newLoc), thePoint.v + HiWord(newLoc), false);
  305.  
  306.         DisposeRgn(dragRgn);                                                                    {dispose of the region and of the port}
  307.         ClosePort(@wPort);
  308.         SetPort(savePort);                                                                        {then restore the old grafPort and exit}
  309.     end;
  310.  
  311. {HideFloats/ShowFloats are simple routines that hide/show all floating windows. They're useful to handle suspend/resume events.}
  312.  
  313.     procedure HideFloats;
  314.     begin
  315.         while isFloating(FrontWindow) do                                                {calling FrontWindow twice is not very efficient, but}
  316.             HideWindow(FrontWindow);                                                        {it's shorter!}
  317.         topFloat := nil;
  318.         bottomFloat := nil;
  319.     end;
  320.  
  321.     procedure ShowFloats;
  322.         const
  323.             WindowList = $09D6;                                                                {this low-mem variable contains the address of the}
  324.         var                                                                                                {first window in the Window Manager's list}
  325.             theWindow, nextWindow: WindowPeek;
  326.             p: ^WindowPeek;
  327.     begin
  328.         p := Pointer(WindowList);
  329.         theWindow := WindowPeek(p^);                                                        {find the first window in the Window Manager's list}
  330.         while (theWindow <> nil) do begin                                                {walk down the list...}
  331.             nextWindow := theWindow^.nextWindow;
  332.             if isFloating(WindowPtr(theWindow)) then
  333.                 SelectTheWindow(WindowPtr(theWindow));                            {..sending every floating window to the front}
  334.             theWindow := nextWindow;
  335.         end;
  336.     end;
  337.  
  338. end.